//==================================================================
//  Copyright (C) 2006  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
///
///
///
///
///
//==================================================================

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <windows.h>
#include <dinput.h>
#include "psys.h"
#include "pgamepad.h"

#define PTHROW	{ PASSERT( 0 ); throw; }

//==================================================================
#define PCUSER_MAX_COUNT	2

static LPDIRECTINPUT8		_dinputp;
static LPDIRECTINPUTDEVICE8	_devicesp[2];
#define SAFE_RELEASE(p)		{ if(p) { (p)->Release(); (p)=NULL; } }

//==================================================================
enum
{
	BUTT_IDX_R_UP = 0,
	BUTT_IDX_R_RIGHT,
	BUTT_IDX_R_DOWN,
	BUTT_IDX_R_LEFT,

	BUTT_IDX_L2,
	BUTT_IDX_R2,
	BUTT_IDX_L1,
	BUTT_IDX_R1,

	BUTT_IDX_START,
	BUTT_IDX_SELECT
};

//-----------------------------------------------------------------------------
// Name: EnumJoysticksCallback()
// Desc: Called once for each enumerated joystick. If we find one, create a
//       device interface on it so we can play with it.
//-----------------------------------------------------------------------------
static BOOL CALLBACK EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance, int *cntp )
{
 HRESULT hr;

	// Obtain an interface to the enumerated joystick.
	hr = _dinputp->CreateDevice( pdidInstance->guidInstance, &_devicesp[ *cntp ], NULL );

	// If it failed, then we can't use this joystick. (Maybe the user unplugged
	// it while we were in the middle of enumerating it.)
	if ( FAILED(hr) ) 
		return DIENUM_CONTINUE;

	// Stop enumeration. Note: we're just taking the first joystick we get. You
	// could store all the enumerated joysticks and let the user pick.

	++(*cntp);

	if ( *cntp >= 2 )
		return DIENUM_STOP;
	else
		return DIENUM_CONTINUE;
}

//-----------------------------------------------------------------------------
// Name: EnumObjectsCallback()
// Desc: Callback function for enumerating objects (axes, buttons, POVs) on a 
//       joystick. This function enables user interface elements for objects
//       that are found to exist, and scales axes min/max values.
//-----------------------------------------------------------------------------
BOOL CALLBACK EnumObjectsCallback( const DIDEVICEOBJECTINSTANCE* pdidoi, int *joy_i )
{
static int nSliderCount = 0;  // Number of returned slider controls
static int nPOVCount = 0;     // Number of returned POV controls

	// For axes that are returned, set the DIPROP_RANGE property for the
	// enumerated axis in order to scale min/max values.
	if ( pdidoi->dwType & DIDFT_AXIS )
	{
	DIPROPRANGE diprg; 

		diprg.diph.dwSize       = sizeof(DIPROPRANGE); 
		diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
		diprg.diph.dwHow        = DIPH_BYID; 
		diprg.diph.dwObj        = pdidoi->dwType; // Specify the enumerated axis
		diprg.lMin              = -1000; 
		diprg.lMax              = +1000; 

		// Set the range for the axis
		if ( FAILED( _devicesp[*joy_i]->SetProperty( DIPROP_RANGE, &diprg.diph ) ) ) 
			return DIENUM_STOP;
	}

	return DIENUM_CONTINUE;
}

//-----------------------------------------------------------------------------
// Desc: Initialize the DirectInput variables.
//-----------------------------------------------------------------------------
static int init_direct_input( HWND hDlg )
{
	HRESULT		hr;

    // Register with the DirectInput subsystem and get a pointer
    // to a IDirectInput interface we can use.
    // Create a DInput object
    if( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, 
                                         IID_IDirectInput8, (VOID**)&_dinputp, NULL ) ) )
        return PERROR;

	int	cnt = 0;
	// Look for a simple joystick we can use for this sample program.
	if ( FAILED( hr = _dinputp->EnumDevices( DI8DEVCLASS_GAMECTRL, 
											 (LPDIENUMDEVICESCALLBACK)EnumJoysticksCallback,
											 &cnt, DIEDFL_ATTACHEDONLY ) ) )
		return PERROR;

	if ( cnt > 2 )
		cnt = 2;

	for (int i=0; i < cnt; ++i)
	{
		// Make sure we got a joystick
		if ( NULL == _devicesp[i] )
		{
			continue;
		}

		// Set the data format to "simple joystick" - a predefined data format 
		//
		// A data format specifies which controls on a device we are interested in,
		// and how they should be reported. This tells DInput that we will be
		// passing a DIJOYSTATE2 structure to IDirectInputDevice::GetDeviceState().
		if ( FAILED( hr = _devicesp[i]->SetDataFormat( &c_dfDIJoystick2 ) ) )
			return PERROR;

		// Set the cooperative level to let DInput know how this device should
		// interact with the system and with other DInput applications.
		if ( FAILED( hr = _devicesp[i]->SetCooperativeLevel( hDlg, DISCL_NONEXCLUSIVE | //DISCL_EXCLUSIVE | 
															DISCL_FOREGROUND ) ) )
			return PERROR;

		// Enumerate the joystick objects. The callback function enabled user
		// interface elements for objects that are found, and sets the min/max
		// values property for discovered axes.
		if ( FAILED( hr = _devicesp[i]->EnumObjects( (LPDIENUMDEVICEOBJECTSCALLBACK)EnumObjectsCallback, &i, DIDFT_ALL ) ) )
			return PERROR;
	}

    return POK;
}

//==================================================================
// Desc: Structure for holding Gamepad data
//==================================================================
struct PCGamepad
{
	// Thumb stick values converted to range [-1,+1]
	float		_stick1_x;
	float		_stick1_y;
	float		_stick2_x;
	float		_stick2_y;

	// Records the state (when last updated) of the buttons.
	// These remain set as long as the button is pressed.
	u_short		_last_buttons;
/*
	bool		_last_left_trig;
	bool		_last_right_trig;

	// Records which buttons were pressed this frame - only set on
	// the frame that the button is first pressed.
*/
	u_short		_pressed_buttons;
/*	bool		_is_pressed_left_trig;
	bool		_is_pressed_right_trig;

	// Device properties
	XINPUT_CAPABILITIES	_caps;
*/
	bool		_is_connected;
/*
	// Flags for whether game pad was just inserted or removed
	bool		_is_inserted;
	bool		_is_removed;

	// The user index associated with this gamepad
	int			_user_idx;
*/
};

//==================================================================
// Desc: Class to manage input devices
//==================================================================
class PCGPManager
{
public:
    // Global access to input states
    static PCGamepad	_gamepads[4];

    // Processes input from the game pad
    static void GetInput( PCGamepad *gpadsp = NULL );
};

//==================================================================
PCGamepad   PCGPManager::_gamepads[4];

//==================================================================
void PGamePad::Update()
{
	static bool	initialized=false;
	if NOT( initialized )
	{
        ZeroMemory( PCGPManager::_gamepads, sizeof(PCGPManager::_gamepads) );
		init_direct_input( (HWND)_contextp );
		initialized = true;
	}

	PCGPManager::GetInput();

	for (int i=0; i < 4; ++i)
	{
		_old_buttons[i] = _buttons[i];
		_buttons[i] = PCGPManager::_gamepads[i]._last_buttons;//mapButtons( PCGPManager::_gamepads[i]._last_buttons );

		_sticks[i][0][0] = PCGPManager::_gamepads[i]._stick1_x;
		_sticks[i][0][1] = PCGPManager::_gamepads[i]._stick1_y;
		_sticks[i][1][0] = PCGPManager::_gamepads[i]._stick2_x;
		_sticks[i][1][1] = PCGPManager::_gamepads[i]._stick2_y;
	}
}

//==================================================================
bool PGamePad::IsConnected( int pad_idx ) const
{
	return PCGPManager::_gamepads[pad_idx]._is_connected;
}
/*
//==================================================================
u_int PGamePad::mapButtons( u_int inbuttons )
{
	u_int outbuttons = 0;
	if ( inbuttons & XINPUT_GAMEPAD_DPAD_UP )			outbuttons |= DPAD_UP;
	if ( inbuttons & XINPUT_GAMEPAD_DPAD_DOWN )			outbuttons |= DPAD_DOWN;
	if ( inbuttons & XINPUT_GAMEPAD_DPAD_LEFT )			outbuttons |= DPAD_LEFT;
	if ( inbuttons & XINPUT_GAMEPAD_DPAD_RIGHT )		outbuttons |= DPAD_RIGHT;
	if ( inbuttons & XINPUT_GAMEPAD_START )				outbuttons |= BUTT_START;
	if ( inbuttons & XINPUT_GAMEPAD_BACK )				outbuttons |= BUTT_BACK;
	if ( inbuttons & XINPUT_GAMEPAD_LEFT_THUMB )		outbuttons |= LEFT_THUMB;
	if ( inbuttons & XINPUT_GAMEPAD_RIGHT_THUMB )		outbuttons |= RIGHT_THUMB;
	if ( inbuttons & XINPUT_GAMEPAD_LEFT_SHOULDER )		outbuttons |= LEFT_SHOULDER;
	if ( inbuttons & XINPUT_GAMEPAD_RIGHT_SHOULDER )	outbuttons |= RIGHT_SHOULDER;
	if ( inbuttons & XINPUT_GAMEPAD_A )					outbuttons |= BUTT_A;
	if ( inbuttons & XINPUT_GAMEPAD_B )					outbuttons |= BUTT_B;
	if ( inbuttons & XINPUT_GAMEPAD_X )					outbuttons |= BUTT_X;
	if ( inbuttons & XINPUT_GAMEPAD_Y )					outbuttons |= BUTT_Y;

	return outbuttons;
}
*/

//===============================================================================
#define BLIND_RANGE	400

static float normalize_stick( int val )
{
	// sets ranges from -1000+BLIND_RANGE to 0 and from 0 to 1000-BLIND_RANGE
	if ( val > 0 )
	{
		val -= BLIND_RANGE;
		if ( val < 0 )
			val = 0;
	}
	else
	{
		val += BLIND_RANGE;
		if ( val > 0 )
			val = 0;
	}

	return val * (1.0f / (1000 - BLIND_RANGE));
}


//-----------------------------------------------------------------------------
// Desc: Get the input device's state and display it.
//-----------------------------------------------------------------------------
static void updateInputState( LPDIRECTINPUTDEVICE8 joyp, u_int *kp, float lever_left[2], float lever_right[2] ) throw(...)
{
HRESULT     hr;
DIJOYSTATE2 js;           // DInput joystick state 

	// it may easily fail.. so always reset when entering !!
	u_int	k = *kp = 0;
	for (int i=0; i < 2; ++i)
	{
		lever_left[i] = 0;
		lever_right[i] = 0;
	}

    if NOT( joyp )
        return;

    // Poll the device to read the current state
    hr = joyp->Poll(); 
    if ( FAILED(hr) )  
    {
        // DInput is telling us that the input stream has been
        // interrupted. We aren't tracking any state between polls, so
        // we don't have any special reset that needs to be done. We
        // just re-acquire and try again.
        hr = joyp->Acquire();
        while( hr == DIERR_INPUTLOST )
		{
            hr = joyp->Acquire();
		}

        // hr may be DIERR_OTHERAPPHASPRIO or other errors.  This
        // may occur when the app is minimized or in the process of 
        // switching, so just try again later 
        return; 
    }

    // Get the input's device state
    if ( FAILED( hr = joyp->GetDeviceState( sizeof(DIJOYSTATE2), &js ) ) )
	{
		PTHROW; // The device should have been acquired during the Poll()
	}

    // Display joystick state to dialog
	lever_left[0] = normalize_stick( js.lX );
	lever_left[1] = normalize_stick( -js.lY );
	lever_right[0] = normalize_stick( js.lZ );
	lever_right[1] = normalize_stick( -js.lRz );
	//bool	done = false;


#define PAD_UP		(((36000)/4)*0)
#define PAD_RIGHT	(((36000)/4)*1)
#define PAD_DOWN	(((36000)/4)*2)
#define PAD_LEFT	(((36000)/4)*3)

	switch ( js.rgdwPOV[0] )
	{
	case PAD_UP:	k |= PGamePad::DPAD_UP;		break;
	case PAD_RIGHT:	k |= PGamePad::DPAD_RIGHT;	break;
	case PAD_DOWN:	k |= PGamePad::DPAD_DOWN;	break;
	case PAD_LEFT:	k |= PGamePad::DPAD_LEFT;		break;

	case (PAD_UP+PAD_RIGHT)/2:		k |= PGamePad::DPAD_UP | PGamePad::DPAD_RIGHT; break;
	case (PAD_RIGHT+PAD_DOWN)/2:	k |= PGamePad::DPAD_RIGHT | PGamePad::DPAD_DOWN; break;
	case (PAD_DOWN+PAD_LEFT)/2:		k |= PGamePad::DPAD_DOWN | PGamePad::DPAD_LEFT; break;
	case (PAD_LEFT+36000)/2:		k |= PGamePad::DPAD_LEFT | PGamePad::DPAD_UP; break;
	}
	

	for(int i = 0; i < 128; i++ )
	{
		if ( js.rgbButtons[i] & 0x80 )
		{
			switch ( i )
			{
			case BUTT_IDX_R_UP:		k |= PGamePad::BUTT_Y;			break;
			case BUTT_IDX_R_RIGHT:	k |= PGamePad::BUTT_B;			break;
			case BUTT_IDX_R_DOWN:	k |= PGamePad::BUTT_A;			break;
			case BUTT_IDX_R_LEFT:	k |= PGamePad::BUTT_X;			break;

			case BUTT_IDX_L2:		k |= PGamePad::LEFT_THUMB;		break;
			case BUTT_IDX_R2:		k |= PGamePad::RIGHT_THUMB;		break;
			case BUTT_IDX_L1:		k |= PGamePad::LEFT_SHOULDER;	break;
			case BUTT_IDX_R1:		k |= PGamePad::RIGHT_SHOULDER;	break;

			case BUTT_IDX_START:	k |= PGamePad::BUTT_START;		break;
			case BUTT_IDX_SELECT:	k |= PGamePad::BUTT_BACK;		break;
			}
			//KSYS_DEBUG_PRINTF( "%i ", i );
			//done = true;
		}
	}
	//if ( done )
	//	KSYS_DEBUG_PRINTF( "\n" );

	*kp = k;
}

//------------------------------------------------------------------
// Desc: Processes input from the gamepads
//------------------------------------------------------------------
void PCGPManager::GetInput( PCGamepad *gpadsp )
{
	u_int	k;

    // If the user did not specify a list of gamepads, use the global list
    if NOT( gpadsp )
        gpadsp = _gamepads;

    // Loop through all gamepads
    for (int i=0; i < PCUSER_MAX_COUNT; ++i)
    {
		/*
        // Read the input state
        XINPUT_STATE InputState;
        bool _was_connected = gpadsp[i]._is_connected;
        gpadsp[i]._is_connected = ( XInputGetState( i, &InputState ) == ERROR_SUCCESS ) ? true : false;

        // Track insertion and removals
        gpadsp[i]._is_removed  = (  _was_connected && !gpadsp[i]._is_connected ) ? true : false;
        gpadsp[i]._is_inserted = ( !_was_connected &&  gpadsp[i]._is_connected ) ? true : false;
		*/

		gpadsp[i]._is_connected = (_devicesp[i] != NULL);

        if NOT( gpadsp[i]._is_connected )
            continue;

		/*
        // Store the capabilities of the device
        if ( gpadsp[i]._is_inserted )
        {
            ZeroMemory( &gpadsp[i], sizeof(_gamepads[i]) );
            gpadsp[i]._is_connected = true;
            gpadsp[i]._is_inserted  = true;
            XInputGetCapabilities( i, XINPUT_FLAG_GAMEPAD, &gpadsp[i]._caps );
        }
		*/

		float	lever_left[2];
		float	lever_right[2];

		updateInputState( _devicesp[i], &k, lever_left, lever_right );

        // Put Xbox device input for the gamepad into our custom format
        gpadsp[i]._stick1_x = lever_left[0];
        gpadsp[i]._stick1_y = lever_left[1];
        gpadsp[i]._stick2_x = lever_right[0];
        gpadsp[i]._stick2_y = lever_right[1];

        // Get the boolean buttons that have been pressed since the last
        // call. Each button is represented by one bit.
        gpadsp[i]._pressed_buttons = ( gpadsp[i]._last_buttons ^ k ) & k;
        gpadsp[i]._last_buttons    = k;
    }
}
